#ifndef	HEADERS_TYPE_INFO_H
#define HEADERS_TYPE_INFO_H
#include <assert.h>

namespace iloki {

/*! \brief A wrapper class of std::type_info which can be copied and assigned.
 *
 * The std::type_info copy constructor and assignment operator are disabled, 
 * which makes storing std::type_info object impossible. iloki::type_info stores
 * a std::type_info pointer and provides:
 * 1. All the member functions of std::type_info;
 * 2. Value semantics;
 * 3. Seamless comparisons with std::type_info;
 */
class type_info {
public:
	/*! \brief Create a iloki::type_info object representing the type of a 
	 * local Nil class.
	 */
	type_info();
	/*! \brief The copy construct enable conversion from a std::type_info
	 * object to a iloki::type_info object.
	 */
	type_info(const std::type_info &);

	/*! \brief Return the wrapped std::type_info object.*/
	const std::type_info &get() const;
	/*! \brief The wrapper of std::type_info::name().
	 * @return A textual representation of the type.
	 * There's no standardized way of mapping class names to strings, so don't
	 * expect the name.
	 */
	const char *name() const;
	/*! \brief The wrapper of std::type_info::before().
	 * @return True if the type precedes the type of rhs in the collation order.
	 * The collation order is just an INTERNAL order kept by a particular 
	 * implementation and is not necessarily related to the inheritance or 
	 * declaring order.
	 */
	bool before(const type_info &rhs) const;
	
private:
	/*! \brief We use a std::type_info pointer to reference the std::type_info
	 * object returned by typeid(). 
	 *
	 * Because the std::type_info object has static storage, we don't need to 
	 * care about its lifetime. All iloki::type_info members required that this
	 * pointer not nullptr.
	 */
	const std::type_info *_info_ptr;
};

// Implementation
inline type_info::type_info() {
	class Nil {};
	_info_ptr = &typeid(Nil);
}
inline type_info::type_info(const std::type_info &rhs) : _info_ptr(&rhs) {
	assert(_info_ptr);
}

inline const std::type_info &type_info::get() const {
	assert(_info_ptr);
	return *_info_ptr;
}
inline const char *type_info::name() const {
	assert(_info_ptr);
	return _info_ptr->name();
}
inline bool type_info::before(const type_info &rhs) const {
	assert(_info_ptr);
	return _info_ptr->before(*rhs._info_ptr);
}

// Comparison operators

/*! \brief Comparison operators.
 * @return True if the two types describe the same type.
 * A derived type is not considered the same type as any of its base classes.
 */
inline bool operator==(const type_info &lhs, const type_info &rhs) {
	return lhs.get() == rhs.get();
}
inline bool operator<(const type_info &lhs, const type_info &rhs) {
	return lhs.before(rhs);
}
inline bool operator!=(const type_info &lhs, const type_info &rhs) {
	return !(rhs == rhs);
}
inline bool operator>(const type_info &lhs, const type_info &rhs) {
	return rhs < lhs;
}
inline bool operator<=(const type_info &lhs, const type_info &rhs) {
	return !(lhs > rhs);
}
inline bool operator>=(const type_info &lhs, const type_info &rhs) {
	return !(lhs < rhs);
}

}
#endif
